/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package plagiadetector.evaluator;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.logging.Level;
import java.util.logging.Logger;
import plagiadetector.datatype.CaseCounter;
import plagiadetector.datatype.CaseData;
import plagiadetector.datatype.PANCorpus;
import plagiadetector.detector.*;
import plagiadetector.utility.FileUtils;
import plagiadetector.utility.PANXMLParser;




/**
 * Modifications for Evaluator, where the PANCases offset and length adapted with the preprocessing which done by the detectors.
 * @author Dell
 */
public class EvaluatorWithMarker {
    private int processedcorpus; // the number of corpus that processed, whether it is marked as plagiarism or not.
    private int detectedasplagdoc; //the number of corpus that detected as plagiarism.
    private float globalprecision;
    private float globalrecall;
    private float globalgranularity;
    private int globalnumberofdetection;
    private int globalnumberofcases;
    private int globalnumberofdetectedcases;
    public ArrayList<CaseCounter> preprocessedcases; //cases information, after adapted by detector's preprocessing.
    private String marker;
    private StringBuilder corptext;
    private File filebuff;
    private FileWriter fw;
    private FileWriter fw2; //used for writing detection performance for further processing. can be deleted.
        /**
     * Evaluate a Detector with corpus(es). 
     * If the address is a single file, it will test the file. Make sure the file has *.xml and corresponing *.txt. 
     * If its a folder, please make sure it has corresponding *xmls.
     * @param dtct the detector that will be tested, please initialize it first.
     * @param address address of the corpus file.
     */
    public  void EvaluateDetector(Detector dtct, String address) throws IOException{
        //initialize timestamp.
        DateFormat df = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss");
        Date date = new Date();
        long start = System.nanoTime();
        
        //initialize global document counter.
        processedcorpus = 0; 
        detectedasplagdoc = 0;
        
        //initialize global performance counter.
        globalprecision = 0f;
        globalrecall = 0f;
        globalgranularity = 0f;

        //initialize global divider counter.
        globalnumberofcases = 0;
        globalnumberofdetection = 0;
        globalnumberofdetectedcases = 0;
        
        //initialize filewriter.
        fw = new FileWriter("EvaluatorDump\\evadump_"+df.format(date)+"_"+dtct.getClass().getSimpleName()+".txt");
        fw2 = new FileWriter(dtct.getClass().getSimpleName()+"_performancedetail.txt");
        //write timestamp.
        df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        System.out.println("Evaluation for "+dtct.getClass().getName()+", run at "+df.format(date)+"\n");       
        fw.write("Evaluation for "+dtct.getClass().getName()+", run at "+df.format(date)+"\n");
        fw2.write("FORMAT : corpus number, plagdet, precision, recall, granularity \n");
        
        
        //preparation to whether the file is a folder or not.
        filebuff = new File(address);
        boolean isDir = filebuff.isDirectory();
            
        //do detection and evaluation.
        if(!isDir){ //single file case
            EvaluateSingleDoc(dtct, address);
        }else{
            EvaluateDirectory(dtct, address);
        }
        
        //count (and normalize) the total precision, recall, and granularity.  
        globalprecision /= globalnumberofdetection > 0 ? (float)globalnumberofdetection: 1f;
        globalrecall /= globalnumberofcases > 0 ? (float)globalnumberofcases : 1f;
        globalgranularity /= globalnumberofdetectedcases > 0 ? (float)globalnumberofdetectedcases : 1f;
        
        //count the plagdet.
        float plagdet;
        plagdet =  countPlagdet(globalprecision, globalrecall, globalgranularity);
        
        //print results
        System.out.println("###############################################################################################################");
        System.out.println("Final Evaluation : ");
        System.out.println("Processed docs : "+processedcorpus);
        System.out.println("Detected-as-plagiarism docs: "+detectedasplagdoc);
        System.out.println("Precision : "+globalprecision);
        System.out.println("Recall : "+globalrecall);
        System.out.println("Granularity : "+globalgranularity);
        System.out.println("Plagdet : "+plagdet);
        
        fw.write("###############################################################################################################\n");
        fw.write("Final Evaluation : \n");
        fw.write("Processed docs : "+processedcorpus+"\n");
        fw.write("Detected-as-plagiarism docs: "+detectedasplagdoc+"\n");
        fw.write("Precision : "+globalprecision+"\n");
        fw.write("Recall : "+globalrecall+"\n");
        fw.write("Granularity : "+globalgranularity+"\n");
        fw.write("Plagdet : "+plagdet+"\n");
        
        start = (System.nanoTime() - start)/1000000000; //change elapsed time to seconds.
        fw.write("Evaluation finished on "+start/60+" minutes "+start%60+" seconds.");
        fw.close();
        
        fw2.close();
    }
    
    /**
     * Evaluate a detector with single corpus. The result of this method will be added to global variables.
     * @param dtct detector that will be tested.
     * @param addr address of the corpus file.
     */
    private  void EvaluateSingleDoc(Detector dtct, String address) throws IOException{
        fw.write("--------------------------------------------------------------------------------------\n");
        fw.write("Processing corpus : "+address+"\n");
        
        processedcorpus++; //increase the number of Processed docs.
       
        //main local performance variables.
        float precision = 0f;
        float recall = 0f;
        float granularity;

        //main local buffer variables.
        int offset; //case offset buffer
        int length; //case length buffer
        int foundlastindex; //marker last index buffer
        
        //prepare the case counter.
        preprocessedcases.clear();

        //prepare the corpus text and detector.
        PANCorpus panc = PANXMLParser.parseXML(address);
        dtct.initialize();
 
        //prepare corpus text buffer.
        corptext.append(panc.getCorpusText());
        
        //first, insert the marker to all case in the text.
        fw.write("Corpus' real cases : "+panc.plagCases.size()+"\n");
        
        for(int i=0; i < panc.plagCases.size(); i++){     
            offset = panc.plagCases.get(i).offset;
            length = panc.plagCases.get(i).length;

            //insert the marker to the cases.
            corptext.insert(offset+(i*2*marker.length()),marker);
            corptext.insert(offset+length+(((i*2)+1)*marker.length()),marker);
        }        
        
        //set it to the detector, then preprocess it.
        dtct.setRawText(new String(corptext));
        dtct.doPreprocessing();
        
        //take the preprocessed text, then detect the markers.
        foundlastindex = 0;
        int firstmark;
        int secondmark;
        int plagcasessize = panc.plagCases.size();
        
        for(int i=0; i < plagcasessize; i++){            
            firstmark = dtct.preprocessedText.indexOf(marker, foundlastindex);
            secondmark = dtct.preprocessedText.indexOf(marker, firstmark+1);
            
            //change the foundLastIndex.
            foundlastindex = secondmark+1;
            
            //calibrate the markers
            firstmark -=  (i*2*marker.length());
            secondmark -= (((i*2)+1)*marker.length());
            
            //add to the preprocessed cases.
            preprocessedcases.add( new CaseCounter(
                    new CaseData(firstmark,secondmark - firstmark),
                    0));
            
            
        }
        
        //after taken from the text, delete all the markers.
        dtct.preprocessedText = dtct.preprocessedText.replaceAll(marker, "");

        //do the detection process.
        dtct.doSegmentation();
        dtct.doStylometricExtraction();
        dtct.doStylometricQA();
        
        fw.write("Detected "+dtct.getNumOfCases()+" case(s) from the segmented text.\n");

        //take all the detected plagiarisms, then decide whether it intersects with the real cases or not.
        float precbuff;
        float recbuff;
        int roffset;
        int rlength;
        int numrealcases = preprocessedcases.size();
        int numdetection = dtct.getNumOfCases();
        int numdetectedcases = 0;
          
        //add the number of detection and real cases to the global variable
        globalnumberofcases += numrealcases;
        globalnumberofdetection += numdetection;
        
        //check the performance of the detector.
        for(int i = 0; i < numdetection; i++){
            //for every detection,
            
            roffset = dtct.getCase(i).offset;
            rlength = dtct.getCase(i).length;
            
            for(int j = 0; j < numrealcases; j++){
                //check it against the real cases.
            
                int soffset = preprocessedcases.get(j).caseData.offset;
                int slength = preprocessedcases.get(j).caseData.length;
                
                if(isIntersects(roffset, rlength, soffset, slength)){
                    fw.write("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n");
                    fw.write("Detected case number "+i+" intersects with real case number "+j+".\n");
                    fw.write("Detected offset : "+roffset+", length : "+rlength+"\n");
                    fw.write("Real offset : "+soffset+", length : "+slength+"\n");

                    //count the case micro precision and recall that will be printed.                   
                    precbuff = countMicroPrecision(roffset, rlength, soffset, slength);
                    recbuff = countMicroRecall(roffset, rlength, soffset, slength);
                   
                    fw.write("Case precision : "+precbuff+", recall : "+recbuff+"\n");

                    //for the recall, you should consider the intersection of last detection too.
                    if(i > 0 && (isIntersects(dtct.getCase(i-1).offset, dtct.getCase(i-1).length, soffset, slength))){
                        int intersect = countIntersect(roffset, rlength, dtct.getCase(i-1).offset, dtct.getCase(i-1).length);
                        recbuff = countMicroRecall(roffset+intersect, rlength-intersect, soffset, slength);
                        fw.write("intersection with prevoius window : "+intersect+"\n.");
                        fw.write("added micro-recall : "+recbuff+"\n.");
                    }
                    
                    //add it to the global variable
                    precision += precbuff;
                    recall +=recbuff;                                     
                    
                    //increase the case counter
                    preprocessedcases.get(j).occurence++;
                }
            }
        }
        
        
        if(numdetection == 0){//if there's no case found in this corpus.
            fw.write("No plagiarism found in this corpus.\n");
        }else{
            //count the macro precision, macro recall, and granularity for this document.
           
            granularity = 0f;
            for(CaseCounter cc : preprocessedcases){
                if(cc.occurence > 0){
                    numdetectedcases++;
                    granularity += cc.occurence;
                }
            }

            //add the parameters to the global data before it divided for this document performance.
            globalprecision += precision;
            globalrecall += recall;
            globalgranularity += granularity;
            
            globalnumberofdetectedcases += numdetectedcases;
            
            //divide
            precision /= numdetection; //divided with all detection.
            recall = numrealcases> 0 ? (float) recall/numrealcases : 0f; //in case actually there's no real case(s).
            granularity = numdetectedcases > 0? (float) granularity/numdetectedcases : 0f; //in case there's no real case(s) detected.

            
            //increase the detected as plagiarism document counter     
            detectedasplagdoc++;
           
            fw.write("##################################################################################################################################\n");
            fw.write("Local corpus performance\n");
            fw.write("Precision : "+precision+"\n");
            fw.write("Recall : "+recall+"\n");
            fw.write("Granularity : "+granularity+"\n");
            fw.flush(); //write the data to file, and flush the buffer
            
            fw2.write(address.replace("D:\\Kuliah\\Semester 8\\IF4099 Tugas Akhir  II\\Implementasi\\Korpus\\PAN-PC-2011\\intrinsic-detection-corpus\\suspicious-document\\part", "").substring(1).replaceAll("[^\\d]", "")+","+countPlagdet(precision, recall, granularity)+","+precision+","+recall+","+granularity+"\n");
            fw2.flush();
        }
        
        //release the PANCorpus data.
        panc = null;

        //cleanse the corptext.
        corptext.replace(0, corptext.length(), "");
    }
    
    /**
     * Evaluate a detector with the corpus within a directory. The result of this method will be added to evalData.
     * If sub-directory exists, this method will process it recursively.
     * @param dtct detector that will be tested.
     * @param addr address of the corpus directory.
     */
    private  void EvaluateDirectory(Detector dtct, String address) throws IOException{
        System.out.println("Processing directory : "+address);
        fw.write("Processing directory : "+address+"\n");
        
        ArrayList<String> XMLs = FileUtils.getFileNames(address, "*.xml"); //list of XMLs.
        ArrayList<String> subDirs = FileUtils.getSubDirectories(address);  // list of subDirectories.
        
        //process the XMLs first, then the subdirectories.
        
        //process the files as single corpus.
        for(int i=0; i<XMLs.size(); i++){
            EvaluateSingleDoc(dtct, address+"\\"+XMLs.get(i));
        }
        
        //process the subdirectories.        
        for(int i=0; i<subDirs.size(); i++){
           EvaluateDirectory(dtct, address+"\\"+subDirs.get(i));
        }
        
        //release objects
        XMLs = null;
        subDirs = null;
    }
    
    public EvaluatorWithMarker(){
        marker = "xxyyzz";
        corptext = new StringBuilder();
        preprocessedcases = new ArrayList<CaseCounter>();
    }
    
    
    /**
     * Count micro precision from given input.
     * @param roffset offset of the case detected by the detector.
     * @param rlength length of the case detected by the detector.
     * @param soffset offset of the case defined by the corpus.
     * @param slength length of the case defined by the corpus.
     * @return 
     */
    public  float countMicroPrecision(int roffset, int rlength, int soffset, int slength){
        return countIntersect(roffset, rlength, soffset, slength)/(float) rlength;
    }
    
    /**
     * Count micro recall from given input.
     * @param roffset offset of the case detected by the detector.
     * @param rlength length of the case detected by the detector.
     * @param soffset offset of the case defined by the corpus.
     * @param slength length of the case defined by the corpus.
     * @return 
     */
    public  float countMicroRecall(int roffset, int rlength, int soffset, int slength){
        return countIntersect(roffset, rlength, soffset, slength)/ (float) slength;
    }
    
    /**
     * Counts plagdet from given precision, recall, and granularity.
     * @param precision precision input.
     * @param recall recall input.
     * @param granularity granularity input.
     * @return 
     */
    public  float countPlagdet(float precision,  float recall, float granularity){
        float plagdet = 0f;
        if(precision > 0 && recall > 0 && granularity > 0) plagdet = (float)(((2*precision*recall)/(precision+recall))/(Math.log(1+granularity)/Math.log(2)));
        return plagdet;
    }

    /**
     * Check whether a detection intersects with a real case or not.
     * @param roffset offset of the case detected by the detector.
     * @param rlength length of the case detected by the detector.
     * @param soffset offset of the case defined by the corpus.
     * @param slength length of the case defined by the corpus.
     * @return 
     */
    public boolean isIntersects(int roffset, int rlength, int soffset, int slength){
        return (countIntersect(roffset, rlength, soffset, slength) != 0 );
    }
    
    /**
     * Count intersected characters from given input.
     * @param firstoffset first section offset.
     * @param firstlength first section length.
     * @param secondoffset second section offset.
     * @param secondlength second section length.
     * @return intersected characters.
     */
    public int countIntersect(int firstoffset, int firstlength, int secondoffset, int secondlength){
        int intersect = 0;
        
                  if(firstoffset > secondoffset && firstoffset < secondoffset+secondlength){ //roffset inside the real case text
                       
                       if(firstoffset+firstlength <= secondoffset+secondlength){ //r inside/subset of s
//                           System.out.println("CASE E | I");
                             intersect = firstlength;
                       }else{//r intersects s                      
//                           System.out.println("CASE G");
                           intersect = secondoffset+secondlength-firstoffset;
                       }
                   }else if(firstoffset < secondoffset && firstoffset+firstlength > secondoffset){
                       
                       if(firstoffset+firstlength > secondoffset+secondlength){//s inside/subset of r
//                           System.out.println("CASE H");
                           intersect = secondlength;
                       }else{//r intersects s
//                           System.out.println("CASE D | F");
                           intersect = firstoffset+firstlength-secondoffset;
                       }
                   }else if(firstoffset == secondoffset){
//                       System.out.println("CASE A | B | C");
                           intersect = secondlength < firstlength ? secondlength : firstlength;
                           
                   }
        return intersect;
    }
    
    public static void main(String[] args){
        Detector d;
        String corpusaddr;
        EvaluatorWithMarker eva3 = new EvaluatorWithMarker();
      
/******** PAN 2011 DOCUMENT PARTS  ********/

//        corpusaddr = "D:\\Kuliah\\Semester 8\\IF4099 Tugas Akhir  II\\Implementasi\\Korpus\\PAN-PC-2011\\intrinsic-detection-corpus\\suspicious-document\\part1";
//        corpusaddr = "D:\\Kuliah\\Semester 8\\IF4099 Tugas Akhir  II\\Implementasi\\Korpus\\PAN-PC-2011\\intrinsic-detection-corpus\\suspicious-document\\part2";
//        corpusaddr = "D:\\Kuliah\\Semester 8\\IF4099 Tugas Akhir  II\\Implementasi\\Korpus\\PAN-PC-2011\\intrinsic-detection-corpus\\suspicious-document\\part3";
//        corpusaddr = "D:\\Kuliah\\Semester 8\\IF4099 Tugas Akhir  II\\Implementasi\\Korpus\\PAN-PC-2011\\intrinsic-detection-corpus\\suspicious-document\\part4";
//        corpusaddr = "D:\\Kuliah\\Semester 8\\IF4099 Tugas Akhir  II\\Implementasi\\Korpus\\PAN-PC-2011\\intrinsic-detection-corpus\\suspicious-document\\part5";
//        corpusaddr = "D:\\Kuliah\\Semester 8\\IF4099 Tugas Akhir  II\\Implementasi\\Korpus\\PAN-PC-2011\\intrinsic-detection-corpus\\suspicious-document\\part6";
//        corpusaddr = "D:\\Kuliah\\Semester 8\\IF4099 Tugas Akhir  II\\Implementasi\\Korpus\\PAN-PC-2011\\intrinsic-detection-corpus\\suspicious-document\\part7";
//        corpusaddr = "D:\\Kuliah\\Semester 8\\IF4099 Tugas Akhir  II\\Implementasi\\Korpus\\PAN-PC-2011\\intrinsic-detection-corpus\\suspicious-document\\part8";
//        corpusaddr = "D:\\Kuliah\\Semester 8\\IF4099 Tugas Akhir  II\\Implementasi\\Korpus\\PAN-PC-2011\\intrinsic-detection-corpus\\suspicious-document\\part9";
//        corpusaddr = "D:\\Kuliah\\Semester 8\\IF4099 Tugas Akhir  II\\Implementasi\\Korpus\\PAN-PC-2011\\intrinsic-detection-corpus\\suspicious-document\\part10";

/******** PAN 2011 FULL  CORPUS ********/
        
//        corpusaddr = "D:\\Kuliah\\Semester 8\\IF4099 Tugas Akhir  II\\Implementasi\\Korpus\\PAN-PC-2011\\intrinsic-detection-corpus\\suspicious-document\\";
        
/****** CORPUS EXAMPLES *******/
//        corpusaddr = "corpusexamples\\PAN2011";
//          corpusaddr = "corpusexamples\\Experiment For Parameters";

        
/******** SINGULAR CORPUSES ************/
//        corpusaddr = "D:\\Kuliah\\Semester 8\\IF4099 Tugas Akhir  II\\Implementasi\\Korpus\\PAN-PC-2011\\intrinsic-detection-corpus\\suspicious-document";
        
        //best corpus for Stamatatos in default settings - short length
// corpusaddr = "D:\\Kuliah\\Semester 8\\IF4099 Tugas Akhir  II\\Implementasi\\Korpus\\PAN-PC-2011\\intrinsic-detection-corpus\\suspicious-document\\part6\\suspicious-document02794.xml";
        
//   corpusaddr = "D:\\Kuliah\\Semester 8\\IF4099 Tugas Akhir  II\\Implementasi\\Korpus\\PAN-PC-2011\\intrinsic-detection-corpus\\suspicious-document\\part6\\suspicious-document02694.xml";
        
        try {           
            
                 
                    d = new StamaDetector();
                    
                    //full part
                    corpusaddr = "D:\\Kuliah\\Semester 8\\IF4099 Tugas Akhir  II\\Implementasi\\Korpus\\PAN-PC-2011\\intrinsic-detection-corpus\\suspicious-document\\";
                    eva3.EvaluateDetector(d, corpusaddr);
/*                            
                    //long length part
                    corpusaddr = "D:\\Kuliah\\Semester 8\\IF4099 Tugas Akhir  II\\Implementasi\\Korpus\\PAN-PC-2011-Divided\\length\\three\\0"; 
                    eva3.EvaluateDetector(d, corpusaddr);
                    
                     //medium length part
                    corpusaddr = "D:\\Kuliah\\Semester 8\\IF4099 Tugas Akhir  II\\Implementasi\\Korpus\\PAN-PC-2011-Divided\\length\\three\\1";
                    eva3.EvaluateDetector(d, corpusaddr);
                   
                    //short length part
                    corpusaddr = "D:\\Kuliah\\Semester 8\\IF4099 Tugas Akhir  II\\Implementasi\\Korpus\\PAN-PC-2011-Divided\\length\\three\\2";
                    eva3.EvaluateDetector(d, corpusaddr);

  */                  
                    
                    d = new OberDetector();
                    
                    //full part
                    corpusaddr = "D:\\Kuliah\\Semester 8\\IF4099 Tugas Akhir  II\\Implementasi\\Korpus\\PAN-PC-2011\\intrinsic-detection-corpus\\suspicious-document\\";
                    eva3.EvaluateDetector(d, corpusaddr);
        } catch (IOException ex) {
            Logger.getLogger(EvaluatorWithMarker.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
}